#include "StdAfx.h"
#include "AuthCrypt.h"
#include "AuthClient.h"

#pragma pack(push, 1)

struct AuthLogonChallenge
{
	uint8 error;
	uint16 size;
	char gameName[4];
	uint8 version[3];
	uint16 build;
	char plattform[4];
	char OS[4];
	char country[4];
	uint32 timeZone;
	uint32 IP;
	uint8 lenAccName;
};

struct AuthLogonProof
{
	uint8 A[32];
	uint8 M[20];
	uint8 CRC[20];
	uint8 numKeys;
	uint8 unk;
};

#pragma pack(pop)

AuthCrypt::AuthCrypt(AuthClient* client) : 
mClient(client),
N("894B645E89E1535BBDAD5B8B290650530801B18EBFBF5E8FAB3C82872A3E9BB7"),
g(7),
mChlgPassed(false),
mProofPassed(false)
{
	s.SetRand(256);
}

void AuthCrypt::handleAuthChallange(ByteBuffer& buffer) {
	AuthLogonChallenge chlg;
	buffer >> chlg;
	std::vector<uint8> accName(chlg.lenAccName);
	buffer.readBytes(accName);
	accName.push_back(0);
	std::string account = (char*)&accName[0];

	if(sAccountMgr.getAccount(account, mAccount) == false) {
		ByteBuffer buffer;
		buffer << uint16(0) << uint8(4);
		mClient->sendData(buffer);
		return;
	}

	auto hashv = mAccount.GetHash().AsByteArray();
	std::vector<uint8> vHash(mAccount.GetHash().GetNumBytes());
	vHash.assign(hashv, hashv + vHash.size());
	std::reverse(vHash.begin(), vHash.end());

	Crypt::SHA1 hash;
	hash.update(s);
	hash.update(&vHash[0], vHash.size());

	Crypt::BigNumber x(hash.getDigest(), SHA_DIGEST_LENGTH);
	v = g.ModExp(x, N);
	b.SetRand(152);

	Crypt::BigNumber gmod = g.ModExp(b, N);
	B = ((v * 3) + gmod) % N;

	Crypt::BigNumber unk;
	unk.SetRand(128);

	ByteBuffer resp;
	resp << uint8(0) << uint8(0) << uint8(0) << B << (uint8)g.GetNumBytes() << g << (uint8)N.GetNumBytes() << N << s << unk << uint8(0);
	mClient->sendData(resp);
	mChlgPassed = true;
}

void AuthCrypt::handleAuthProof(ByteBuffer& buffer) {
	if(mChlgPassed == false || mAccount.GetUsername() == "") {
		mClient->getSocket()->disconnect();
	}

	AuthLogonProof proof;
	buffer >> proof;

	Crypt::BigNumber A(proof.A, 32);
	Crypt::SHA1 hash;
	hash.update(A);
	hash.update(B);

	Crypt::BigNumber u(hash.getDigest(), SHA_DIGEST_LENGTH);
	auto S = (A * (v.ModExp(u, N))).ModExp(b, N);
	auto t = S.AsByteArray();
	uint8 tHalf1[16];
	uint8 tHalf2[16];
	uint8 fullKey[40];

	for(uint32 i = 0; i < 16; ++i) {
		tHalf1[i] = t[i * 2];
		tHalf2[i] = t[i * 2 + 1];
	}


	hash.clear();
	hash.update(tHalf1, 16);
	
	Crypt::SHA1 hash2;
	hash2.update(tHalf2, 16);

	auto digest = hash.getDigest();
	auto digest2 = hash2.getDigest();

	for(uint32 i = 0; i < 20; ++i) {
		fullKey[i * 2] = digest[i];
		fullKey[i * 2 + 1] = digest2[i];
	}

	mSessionKey.SetBinary(fullKey, 40);
	hash.clear();
	hash.update(N);

	uint8 authHash[SHA_DIGEST_LENGTH];
	hash.getDigest(authHash);

	hash.clear();
	hash.update(g);
	for(uint32 i = 0; i < SHA_DIGEST_LENGTH; ++i) {
		authHash[i] ^= hash.getDigest()[i];
	}

	Crypt::BigNumber t3(authHash, SHA_DIGEST_LENGTH);
	hash.clear();
	hash.update(mAccount.GetUsername());

	Crypt::BigNumber t4(hash.getDigest(), SHA_DIGEST_LENGTH);
	hash.clear();

	hash.update(t3);
	hash.update(t4);
	hash.update(s);
	hash.update(A);
	hash.update(B);
	hash.update(mSessionKey);

	Crypt::BigNumber M(hash.getDigest(), SHA_DIGEST_LENGTH);
	auto marr = M.AsByteArray();
	if(memcmp(M.AsByteArray(), proof.M, min(SHA_DIGEST_LENGTH, 20))) {
		ByteBuffer resp;
		resp << uint16(0) << uint8(4);
		mClient->sendData(resp);
		return;
	}

	hash.clear();
	hash.update(A);
	hash.update(M);
	hash.update(mSessionKey);

	Crypt::BigNumber clientProof(hash.getDigest(), SHA_DIGEST_LENGTH);

	ByteBuffer resp;
	resp << uint8(1) << uint8(0) << clientProof;
	while(resp.getBuffer().size() < 32)
		resp << uint8(0);

	mClient->sendData(resp);
	mProofPassed = true;
}

void AuthCrypt::handleReconnectChlg(ByteBuffer& buff) {
	sLog.notice("AuthCrypt") << __FUNCTION__ << std::endl;
}